#ifndef _OAL_DRV_H_
#define _OAL_DRV_H_

/* Linux header files */
#include        <linux/kernel.h>
#include        <linux/module.h>
#include        <linux/version.h>
#include        <linux/spinlock.h>
#include        <linux/kthread.h>

/****************************
 * Tools Specific
 ****************************/

#ifndef __ATTRIB_ALIGN__
#define __ATTRIB_ALIGN__ __attribute__((aligned(4)))
#endif

#ifndef __ATTRIB_PACK__
#define __ATTRIB_PACK__ __attribute__ ((packed))
#endif

/** Get module */
#define MODULE_GET    try_module_get(THIS_MODULE)
/** Put module */
#define MODULE_PUT    module_put(THIS_MODULE)

/****************************
 * OS Semaphore Specific
 ****************************/
/** Initialize semaphore */
#define OAL_INIT_SEMAPHORE(x)      sema_init(x, 1)
/** Initialize semaphore */
#define OAL_INIT_SEMAPHORE_LOCKED(x)   sema_init(x, 0)

/** Acquire semaphore and with blocking */
#define OAL_ACQ_SEMAPHORE_BLOCK(x)    down_interruptible(x)
/** Acquire semaphore without blocking */
#define OAL_ACQ_SEMAPHORE_NOBLOCK(x)    down_trylock(x)
/** Release semaphore */
#define OAL_REL_SEMAPHORE(x)       up(x)


/** oal_lock */
typedef struct _oal_lock {
    /** Lock */
    spinlock_t lock;
    /** Flags */
    unsigned long flags;
} oal_lock;

/****************************
 * OS Thread Specific
 ****************************/

/** Kernel thread structure */
typedef struct _oal_thread {
    /** Task control structrue */
    struct task_struct *task;
    /** Pointer to wait_queue_head */
    wait_queue_head_t wait_q;
    /** PID */
    pid_t pid;
    /** Pointer to oal_handle */
    void *handle;
} oal_thread;

/**
 *  @brief Activate thread
 *
 *  @param thr            Thread structure
 *  @return            N/A
 */
static inline void
oal_activate_thread(oal_thread *thr)
{
    /** Initialize the wait queue */
    init_waitqueue_head(&thr->wait_q);

    /** Record the thread pid */
    thr->pid = current->pid;
}

/**
 *  @brief De-activate thread
 *
 *  @param thr            Thread structure
 *  @return            N/A
 */
static inline void
oal_deactivate_thread(oal_thread *thr)
{
    /* Reset the pid */
    thr->pid = 0;
}

/**
 *  @brief Create and run the thread
 *
 *  @param threadfunc        Thread function
 *  @param thr            Thread structure
 *  @param param            Thread param
 *  @param name            Thread name
 *  @return            N/A
 */
static inline void
oal_create_thread(int (*threadfunc) (void *), oal_thread *thr, void* param, char *name)
{
    /* Create and run the thread */
    thr->task = kthread_run(threadfunc, param, "%s", name);
}

/**
 *  @brief Stop the thread
 *
 *  @param thr            Thread structure
 *  @return            N/A
 */
static inline void
oal_stop_thread(oal_thread *thr)
{
    /* Stop the thread */
    if (thr->task) {
        kthread_stop(thr->task);
        thr->task = NULL;
    }
}

/**
 *  @brief Schedule timeout
 *
 *  @param millisec    Timeout duration in milli second
 *
 *  @return        N/A
 */
static inline void
oal_sched_timeout(u32 millisec)
{
    set_current_state(TASK_INTERRUPTIBLE);

    schedule_timeout((millisec * HZ) / 1000);
}

/****************************
 * OS timer Specific
 ****************************/
/** Timer structure */
typedef struct _oal_drv_timer {
    /** Timer list */
    struct timer_list tl;
    /** Timer function */
    void (*timer_function) (void *context);
    /** Timer function context */
    void *function_context;
    /** Time period */
    u32 time_period;
    /** Is timer periodic ? */
    u32 timer_is_periodic;
    /** Is timer cancelled ? */
    u32 timer_is_canceled;
} oal_drv_timer, *poal_drv_timer;

/**
 *  @brief Timer handler
 *
 *  @param fcontext    Timer context
 *
 *  @return        N/A
 */
static inline void
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
oal_timer_handler(unsigned long fcontext)
{
    poal_drv_timer timer = (poal_drv_timer)fcontext;
#else
oal_timer_handler(struct timer_list *timer_list)
{
    poal_drv_timer timer = container_of(timer_list, oal_drv_timer, tl);
#endif

    timer->timer_function(timer->function_context);

    if (timer->timer_is_periodic == true) {
        mod_timer(&timer->tl,
              jiffies + ((timer->time_period * HZ) / 1000));
    } else {
        timer->timer_is_canceled = true;
        timer->time_period = 0;
    }
}

/**
 *  @brief Initialize timer
 *
 *  @param timer        Timer structure
 *  @param TimerFunction    Timer function
 *  @param FunctionContext    Timer function context
 *
 *  @return            N/A
 */
static inline void
oal_initialize_timer(poal_drv_timer timer,
              void (*TimerFunction) (void *context),
              void *FunctionContext)
{
    /* First, setup the timer */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
    init_timer(&timer->tl);
    timer->tl.function = oal_timer_handler;
    //timer->tl.data = (ptr)timer;
#else
    timer_setup(&timer->tl, oal_timer_handler, 0);
#endif

    /* Then tell the proxy which function to call and what to pass it */
    timer->timer_function = TimerFunction;
    timer->function_context = FunctionContext;
    timer->timer_is_canceled = true;
    timer->time_period = 0;
    timer->timer_is_periodic = false;
}

/**
 *  @brief Modify timer
 *
 *  @param timer        Timer structure
 *  @param millisecondperiod    Time period in millisecond
 *
 *  @return            N/A
 */
static inline void
oal_mod_timer(poal_drv_timer timer, u32 millisecondperiod)
{
    timer->time_period = millisecondperiod;
    mod_timer(&timer->tl, jiffies + (millisecondperiod * HZ) / 1000);
    timer->timer_is_canceled = false;
}

/**
 *  @brief Cancel timer
 *
 *  @param timer    Timer structure
 *
 *  @return        N/A
 */
static inline void
oal_cancel_timer(oal_drv_timer *timer)
{
    if (timer->timer_is_periodic || in_atomic() || irqs_disabled())
        del_timer(&timer->tl);
    else
        del_timer_sync(&timer->tl);
    timer->timer_is_canceled = true;
    timer->time_period = 0;
}


/****************************
 * OS spin lock specific
 ****************************/

/**
 *  @brief Lock the spin_lock
 *
 *  @param poal_handle Pointer to the OAL context
 *  @param pplock   Pointer to the lock
 *
 *  @return         0 for success, otherwise fail.
 */
static inline int
oal_spin_lock(void *poal_handle, void *plock)
{
    oal_lock *mlock = (oal_lock *) plock;
    unsigned long flags = 0;

    if (mlock) {
        spin_lock_irqsave(&mlock->lock, flags);
        mlock->flags = flags;
        return 0;
    } else {
        return -EBUSY;
    }
}

/**
 *  @brief Unlock the spin_lock
 *
 *  @param poal_handle Pointer to the OAL context
 *  @param pplock   Pointer to the lock
 *
 *  @return         0 for success, otherwise fail.
 */
static inline int
oal_spin_unlock(void *poal_handle, void *plock)
{
    oal_lock *mlock = (oal_lock *) plock;

    if (mlock) {
        spin_unlock_irqrestore(&mlock->lock, mlock->flags);

        return 0;
    } else {
        return -EBUSY;
    }
}

/**
 *  @brief Initializes the spin_lock
 *
 *  @param poal_handle Pointer to the OAL context
 *  @param pplock   Pointer to the lock
 *
 *  @return         0 for success, otherwise fail.
 */
static inline int
oal_init_lock(void * poal_handle, void ** pplock)
{
    // oal_handle *handle = (oal_handle *) poal_handle;
    oal_lock *mlock = NULL;

    mlock = (oal_lock *) kmalloc(sizeof(oal_lock), GFP_ATOMIC);
    if (!mlock) {
        return -ENOMEM;
    }
    spin_lock_init(&mlock->lock);
    *pplock = (void *) mlock;

    // atomic_inc(&handle->lock_count);

    return 0;
}

/**
 *  @brief Free the spin lock
 *
 *  @param poal_handle Pointer to the OAL context
 *  @param plock    Lock
 *
 *  @return         0 for success, otherwise fail.
 */
static inline int
oal_free_lock(void * poal_handle, void * plock)
{
    // oal_handle *handle = (oal_handle *) poal_handle;
    oal_lock *mlock = plock;

    kfree(mlock);
    // if (mlock)
    //     atomic_dec(&handle->lock_count);

    return 0;
}

#endif